package edu.hcmus.sow.dao.security;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;

import edu.hcmus.sow.dao.SiteDAO;
import edu.hcmus.sow.domain.security.CustomUser;
import edu.hcmus.sow.web.security.CustomWebAuthenticationDetails;

/**
 * @see org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl
 * 
 * @author Tony Phuong
 * 
 */
public class CustomJdbcDaoImpl extends JdbcDaoImpl {

   @Autowired
   SiteDAO siteDao;

   private String usersByUsernameQuery;
   private String authoritiesByUsernameQuery = "select ua.user_id, au.authority from authorities au, user_authorities ua where (au.id=ua.authorities_id) and (ua.user_id=?)";
   private String groupAuthoritiesByUsernameQuery = "select g.id, g.name, au.authority from `group` g, user_group ug, authorities au, group_authorities ga where (g.id=ug.group_id) and (g.id=ga.group_id) and (au.id=ga.authorities_id) and (ug.user_id=?)";

   @SuppressWarnings("deprecation")
   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      List<UserDetails> users = loadUsersByUsername(username);

      if (users.size() == 0) {
         logger.debug("Query returned no results for user '" + username + "'");

         throw new UsernameNotFoundException(messages.getMessage("JdbcDaoImpl.notFound", new Object[] { username },
               "Username {0} not found"), username);
      }

      // UserDetails user = users.get(0); // contains no GrantedAuthority[]
      CustomUser user = (CustomUser) users.get(0);

      Set<GrantedAuthority> dbAuthsSet = new HashSet<GrantedAuthority>();

      dbAuthsSet.addAll(loadUserAuthorities(user.getUserID()));

      dbAuthsSet.addAll(loadGroupAuthorities(user.getUserID()));

      List<GrantedAuthority> dbAuths = new ArrayList<GrantedAuthority>(dbAuthsSet);

      addCustomAuthorities(user.getUsername(), dbAuths);

      //System.out.println(SecurityContextHolder.getContext().getAuthentication().getDetails());

      return createUserDetails(username, user, dbAuths);
   }

   /**
    * Executes the SQL <tt>usersByUsernameQuery</tt> and returns a list of UserDetails objects.
    * There should normally only be one matching user.
    */
   @Override
   protected List<UserDetails> loadUsersByUsername(String username) {
      Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
      CustomWebAuthenticationDetails details = (CustomWebAuthenticationDetails) authentication.getDetails();
      String subDomain = details.getSubDomain();

      if (StringUtils.isEmpty(subDomain)) {
         usersByUsernameQuery = "select username,password,enabled,id,client_id from user where client_id is null and username=?";
         return getJdbcTemplate().query(usersByUsernameQuery, new String[] { username }, new RowMapper<UserDetails>() {
            public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
               String username = rs.getString(1);
               String password = rs.getString(2);
               boolean enabled = rs.getBoolean(3);
               Integer userID = rs.getInt(4);
               Integer clientID = rs.getInt(5);
               return new CustomUser(userID, clientID, username, password, enabled, true, true, true,
                     AuthorityUtils.NO_AUTHORITIES);
            }
         });
      }

      int clientID;
      try {
         clientID = siteDao.findBySubDomain(subDomain).getClient().getId();
      } catch (Exception ex) {
         throw new RuntimeException("Sub domain not found: " + subDomain);
      }

      // Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
      // System.out.println((String)((UserDetails) principal).getUsername());

      usersByUsernameQuery = "select username,password,enabled,id,client_id from user where username=? and client_id=?";
      return getJdbcTemplate().query(usersByUsernameQuery, new Object[] { username, clientID },
            new RowMapper<UserDetails>() {
               public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
                  String username = rs.getString(1);
                  String password = rs.getString(2);
                  boolean enabled = rs.getBoolean(3);
                  Integer userID = rs.getInt(4);
                  Integer clientID = rs.getInt(5);
                  return new CustomUser(userID, clientID, username, password, enabled, true, true, true,
                        AuthorityUtils.NO_AUTHORITIES);
               }
            });

   }

   /**
    * Loads authorities by executing the SQL from <tt>authoritiesByUsernameQuery</tt>.
    * 
    * @return a list of GrantedAuthority objects for the user
    */
   protected List<GrantedAuthority> loadUserAuthorities(Integer userID) {
      return getJdbcTemplate().query(authoritiesByUsernameQuery, new Object[] { userID },
            new RowMapper<GrantedAuthority>() {
               public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
                  String roleName = getRolePrefix() + rs.getString(2);

                  return new SimpleGrantedAuthority(roleName);
               }
            });
   }

   /**
    * Loads authorities by executing the SQL from <tt>groupAuthoritiesByUsernameQuery</tt>.
    * 
    * @return a list of GrantedAuthority objects for the user
    */
   protected List<GrantedAuthority> loadGroupAuthorities(Integer userID) {
      return getJdbcTemplate().query(groupAuthoritiesByUsernameQuery, new Object[] { userID },
            new RowMapper<GrantedAuthority>() {
               public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
                  String roleName = getRolePrefix() + rs.getString(3);

                  return new SimpleGrantedAuthority(roleName);
               }
            });
   }

   /**
    * Can be overridden to customize the creation of the final UserDetailsObject which is returned
    * by the <tt>loadUserByUsername</tt> method.
    * 
    * @param username
    *           the name originally passed to loadUserByUsername
    * @param userFromUserQuery
    *           the object returned from the execution of the
    * @param combinedAuthorities
    *           the combined array of authorities from all the authority loading queries.
    * @return the final UserDetails which should be used in the system.
    */
   @Override
   protected UserDetails createUserDetails(String username, UserDetails userFromUserQuery,
         List<GrantedAuthority> combinedAuthorities) {
      String returnUsername = userFromUserQuery.getUsername();
      Integer userID = ((CustomUser) userFromUserQuery).getUserID();
      Integer clientID = ((CustomUser) userFromUserQuery).getClientID();

      // if (!usernameBasedPrimaryKey) {
      // returnUsername = username;
      // }

      return new CustomUser(userID, clientID, returnUsername, userFromUserQuery.getPassword(),
            userFromUserQuery.isEnabled(), true, true, true, combinedAuthorities);
   }
}
